export function bind(fn, thisArg) {
  return function wrap(...args) {
    return fn.apply(thisArg, args);
  };
}

export function extend(a, b, thisArg) {
  Object.getOwnPropertyNames(b).forEach(function assignValue(key) {
    const val = b[key];
    if (thisArg && typeof val === 'function') {
      a[key] = bind(val, thisArg);
    } else {
      a[key] = val;
    }
  });
  return a;
}

import dayjs from 'dayjs';

/**
 * 设置双向绑定值,只会设置vo有的属性
 * @param vo 目标
 * @param data 源
 */
export const setVo = (vo, data) => {
  if (typeof data !== 'object' || !data || !vo) {
    return;
  }
  Object.keys(vo)
    .filter(key => Reflect.has(data, key))
    .forEach(key => {
      const val = data[key];
      if (Array.isArray(val)) {
        if (Array.isArray(vo[key])) {
          val.forEach((v, index) => {
            const vv = vo[key][index];
            if (!vv) {
              vo[key].splice(index, 1, typeof v === 'object' ? { ...v } : v);
            } else {
              setVo(vv, v);
            }
          });
        } else if (!vo[key]) {
          vo[key] = val;
        }
        return;
      }
      if (typeof val === 'object') {
        setVo(vo[key], data[key]);
        return;
      }
      vo[key] = data[key];
    });
};

/**
 * 身份证号码验证
 * @param {Number | String} code 身份证号码
 * @returns Boolean
 */
export const validateIdCard = code => {
  const city = {
    11: '北京',
    12: '天津',
    13: '河北',
    14: '山西',
    15: '内蒙古',
    21: '辽宁',
    22: '吉林',
    23: '黑龙江 ',
    31: '上海',
    32: '江苏',
    33: '浙江',
    34: '安徽',
    35: '福建',
    36: '江西',
    37: '山东',
    41: '河南',
    42: '湖北 ',
    43: '湖南',
    44: '广东',
    45: '广西',
    46: '海南',
    50: '重庆',
    51: '四川',
    52: '贵州',
    53: '云南',
    54: '西藏 ',
    61: '陕西',
    62: '甘肃',
    63: '青海',
    64: '宁夏',
    65: '新疆',
    71: '台湾',
    81: '香港',
    82: '澳门',
    91: '国外 '
  };

  if (
    !code ||
    !/^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$|^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X|x)$/i.test(
      code
    ) ||
    !city[code.substr(0, 2)]
  ) {
    return false;
  }
  // 18位身份证需要验证最后一位校验位
  if (code.length === 18) {
    code = [...code];
    // ∑(ai×Wi)(mod 11)
    // 加权因子
    const factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
    // 校验位
    const parity = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2', 'x'];
    let sum = 0;
    for (let i = 0; i < 17; i++) {
      sum += code[i] * factor[i];
    }

    return parity[sum % 11] === code[17];
  }
  return true;
};

/**
 * 强制小数位 例4.5 => 4.50; 1 => 1.00; 1.123 => 1.12
 * @param {Number | String} num 源数字
 * @param {Number} count 小数位数
 * @param {Boolean} fill 是否填充
 */
export const forceDecimal = (num, count = 2, fill = true) => {
  if (!num) {
    if (!fill || count <= 0) {
      return '';
    }
    return `0.${''.padEnd(count, '0')}`;
  }
  const str = num.toString();
  const [start, end = ''] = str.split('.');
  if (count <= 0) {
    return start;
  }
  if (end.length <= count && !fill) {
    if (end.length <= 0) {
      return start;
    }
    return `${start}.${end}`;
  }
  return `${start}.${
    end.length > count ? end.substring(0, count) : end.padEnd(count, '0')
  }`;
};

/**
 * 重置VO
 * @param vo
 */
export const resetVo = vo => {
  if (!vo) {
    return null;
  }
  Object.keys(vo).forEach(key => {
    if (Array.isArray(vo[key])) {
      if (vo[key].length) {
        vo[key] = resetVo(vo[key][0]);
      }
      return;
    }
    if (typeof vo[key] === 'object') {
      vo[key] = resetVo(vo[key]);
      return;
    }
    vo[key] = null;
  });
  return vo;
};

/**
 * 线性颜色渐变
 * @param min 最小参考值
 * @param max 最大参考值
 * @param startRgb 开始颜色
 * @param endRgb 结束颜色
 * @param num 当前参考值
 * @returns {number[]}
 */
export const colorGradient = (min, max, startRgb, endRgb, num) => {
  return Array.from({ length: 3 }).map((_, index) =>
    parseInt(
      ((endRgb[index] - startRgb[index]) * (num - min)) / (max - min) +
        startRgb[index]
    )
  );
};

/**
 * 分钟数转小时，125分钟 => 2
 * @param minute 分钟数
 * @returns {string}
 */
export const minuteToHours = minute => String(Math.floor(minute / 60));

/**
 * 分钟数转分钟，125分钟 => 5
 * @param minute
 * @returns {string}
 */
export const minuteToMinute = minute => String(Math.floor(minute % 60));

/**
 * 获取元素距离顶部的距离
 * @param e 元素
 * @returns {*}
 */
export const getOffsetTop = e => {
  const { offsetTop, offsetParent } = e;
  let offset = offsetTop;
  if (offsetParent != null) {
    offset += getOffsetTop(offsetParent);
  }
  return offset;
};

/**
 * 判断是否为手机
 * @returns {boolean}
 */
export const isPhone = (() => {
  const p = navigator.platform.toUpperCase();
  const pc = ['WIN', 'MAC', 'X11'];
  return !pc.some(s => p.startsWith(s));
})();

/**
 * base64转Blob
 * @param base64
 * @returns {Blob}
 */
export const base64toBlob = base64 => {
  const arr = base64.split(',');
  const mime = arr[0].match(/:(.*?);/)[1];
  const bstr = atob(arr[1]);
  let n = bstr.length;
  const u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new Blob([u8arr], { type: mime });
};
/**
 * blob转URL
 * @param file
 * @returns {string|null}
 */
export const getObjectURL = file => {
  if (window.createObjectURL !== undefined) {
    // basic
    return window.createObjectURL(file);
  } else if (window.URL !== undefined) {
    // mozilla(firefox)
    return window.URL.createObjectURL(file);
  } else if (window.webkitURL !== undefined) {
    // webkit or chrome
    return window.webkitURL.createObjectURL(file);
  }
  return null;
};

/**
 * base64转URL
 * @param base64
 * @returns {string|null}
 */
export const base64ToUrl = base64 => getObjectURL(base64toBlob(base64));

/**
 * 下载文件
 * @param url
 * @param name
 */
export const downloadOfUrl = (url, name) => {
  const link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
  link.href = url;
  link.download = name;
  link.click();
};

/**
 * 获取文件大小格式
 * @param size
 * @returns {{power: number, text: string}|{power: number, text: string}|{power: number, text: string}|{power: number, text: string}}
 */
export const getFileSizeFormat = size => {
  const format = [
    {
      power: 0,
      text: 'KB'
    },
    {
      power: 2,
      text: 'M'
    },
    {
      power: 3,
      text: 'GB'
    },
    {
      power: 4,
      text: 'TB'
    }
  ];
  const [first] = format;
  return format.reduce((v, f) => {
    const val = Math.pow(1024, f.power);
    return size > val ? f : v;
  }, first);
};

/**
 * 获取文件大小描述，1200KB，1M，0.1GB，0.01TB
 * @param size
 * @returns {string}
 */
export const getFileSizeText = size => {
  const format = getFileSizeFormat(size);
  const value = Array.from({ length: format.power }).reduce(
    acc => acc / 1024,
    size
  );
  return `${value}${format.text}`;
};

/**
 * 代理调用父级函数 this.$super(A).some()
 * @param target 父级对象
 * @returns {*}
 */
export const $super = function(target) {
  return new Proxy(target, {
    get: (options, name) => {
      if (options.methods && name in options.methods) {
        return options.methods[name].bind(this);
      }
    }
  });
};

/**
 * 把数组转换为树形结构
 * @param {Array} array 源数据
 * @param {String | Number} pid 根ID 默认:0
 * @param {String} parentProp 父级属性名称 默认:pid
 * @param {String} idProp ID属性名称 默认:id
 * @param {String} childProp 下级属性名称 默认:children
 * @param {Function} renderer 渲染函数
 */
export const makeTree = (
  array,
  pid = 0,
  parentProp = 'pid',
  idProp = 'id',
  childProp = 'children',
  renderer = () => {}
) => {
  const result = [];
  let temp = [];
  for (const item of array) {
    if (
      item[parentProp] === pid ||
      (pid === 0 &&
        (item[parentProp] === undefined || item[parentProp] === null))
    ) {
      const clone = Object.assign({}, item);
      result.push(clone);
      temp = makeTree(
        array,
        item[idProp],
        parentProp,
        idProp,
        childProp,
        renderer
      );
      if (temp.length > 0) {
        clone[childProp] = temp;
      }
      if (typeof renderer === 'function') {
        renderer(clone);
      }
    }
  }
  return result;
};

/**
 * 转换日期范围 [date1, date2] => {[start]: '2020-01-01 00:00:00', [end]: '2020-01-01 23:59:59'}
 * @param dateRange 日期范围值
 * @param start 开始key
 * @param end 结束key
 * @param format 格式化
 * @return {{}}
 */
export const parseDateRange = (
  dateRange,
  [start, end] = ['start', 'end'],
  format = ['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59']
) => {
  if (dateRange?.length === 2 && dateRange.every(d => dayjs(d).isValid())) {
    const startFormat = typeof format === 'string' ? format : format[0];
    const endFormat = typeof format === 'string' ? format : format[1];
    return {
      [start]: dayjs(dateRange[0]).format(startFormat),
      [end]: dayjs(dateRange[1]).format(endFormat)
    };
  }
  return { [start]: null, [end]: null };
};

/**
 * 转换月份范围 [date1, date2] => {[start]: '2020-01-01 00:00:00', [end]: '2020-01-31 23:59:59'}
 * @param dateRange 日期范围值
 * @param start 开始key
 * @param end 结束key
 * @param format 格式化
 * @returns {{}}
 */
export const parseMonthRange = (
  dateRange,
  [start, end] = ['start', 'end'],
  format = ['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59']
) => {
  let range = dateRange;
  if (range?.length === 2) {
    range = [
      dayjs(dateRange[0])
        .startOf('month')
        .toDate(),
      dayjs(dateRange[1])
        .endOf('month')
        .toDate()
    ];
  }
  return parseDateRange(range, [start, end], format);
};

/**
 * 根据身份证号获取出生日期
 * @param idCard 身份证号
 * @returns {number} 时间戳
 */
export const getBirthdayDateFromIdCard = idCard => {
  let birthDay, birthDayTemp;
  if (!validateIdCard(idCard)) {
    return 0;
  }
  if (idCard.length === 18) {
    birthDay = idCard.substring(6, 14);
  } else if (idCard.length === 15) {
    birthDayTemp = idCard.substring(6, 12);
    birthDay = `19${birthDayTemp}`;
  } else {
    return 0;
  }
  const birth = `${birthDay.substring(0, 4)}-${birthDay.substring(
    4,
    6
  )}-${birthDay.substring(6, 8)}`;
  return dayjs(birth).valueOf();
};

/**
 * 动画滑动滚动条
 * @param element 元素
 * @param speed 速度
 */
export const animateScrollToTop = (element, speed) => {
  const rect = element.getBoundingClientRect();
  // 获取元素相对窗口的top值，此处应加上窗口本身的偏移
  const top = window.pageYOffset + rect.top;
  let currentTop = 0;
  let requestId;
  // 采用requestAnimationFrame，平滑动画
  function step() {
    currentTop += speed;
    if (currentTop <= top) {
      window.scrollTo(0, currentTop);
      requestId = window.requestAnimationFrame(step);
    } else {
      window.cancelAnimationFrame(requestId);
    }
  }
  window.requestAnimationFrame(step);
};
